home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / monkeysphere / keytrans < prev    next >
Encoding:
Text File  |  2011-03-24  |  31.6 KB  |  1,189 lines

  1. #!/usr/bin/perl -T
  2.  
  3. # keytrans: this is an RSA key translation utility; it is capable of
  4. # transforming RSA keys (both public keys and secret keys) between
  5. # several popular representations, including OpenPGP, PEM-encoded
  6. # PKCS#1 DER, and OpenSSH-style public key lines.
  7.  
  8. # How it behaves depends on the name under which it is invoked.  The
  9. # two implementations currently are: pem2openpgp and openpgp2ssh.
  10.  
  11.  
  12.  
  13. # pem2openpgp: take a PEM-encoded RSA private-key on standard input, a
  14. # User ID as the first argument, and generate an OpenPGP secret key
  15. # and certificate from it.
  16.  
  17. # WARNING: the secret key material *will* appear on stdout (albeit in
  18. # OpenPGP form) -- if you redirect stdout to a file, make sure the
  19. # permissions on that file are appropriately locked down!
  20.  
  21. # Usage:
  22.  
  23. # pem2openpgp 'ssh://'$(hostname -f) < /etc/ssh/ssh_host_rsa_key | gpg --import
  24.  
  25.  
  26.  
  27.  
  28. # openpgp2ssh: take a stream of OpenPGP packets containing public or
  29. # secret key material on standard input, and a Key ID (or fingerprint)
  30. # as the first argument.  Find the matching key in the input stream,
  31. # and emit it on stdout in an OpenSSH-compatible format.  If the input
  32. # key is an OpenPGP public key (either primary or subkey), the output
  33. # will be an OpenSSH single-line public key.  If the input key is an
  34. # OpenPGP secret key, the output will be a PEM-encoded RSA key.
  35.  
  36. # Example usage:
  37.  
  38. # gpg --export-secret-subkeys --export-options export-reset-subkey-passwd $KEYID | \
  39. #  openpgp2ssh $KEYID | ssh-add /dev/stdin
  40.  
  41.  
  42. # Authors:
  43. #  Jameson Rollins <jrollins@finestructure.net>
  44. #  Daniel Kahn Gillmor <dkg@fifthhorseman.net>
  45.  
  46. # Started on: 2009-01-07 02:01:19-0500
  47.  
  48. # License: GPL v3 or later (we may need to adjust this given that this
  49. # connects to OpenSSL via perl)
  50.  
  51. use strict;
  52. use warnings;
  53. use File::Basename;
  54. use Crypt::OpenSSL::RSA;
  55. use Crypt::OpenSSL::Bignum;
  56. use Crypt::OpenSSL::Bignum::CTX;
  57. use Digest::SHA;
  58. use MIME::Base64;
  59. use POSIX;
  60.  
  61. ## make sure all length() and substr() calls use bytes only:
  62. use bytes;
  63.  
  64. my $old_format_packet_lengths = { one => 0,
  65.                   two => 1,
  66.                   four => 2,
  67.                   indeterminate => 3,
  68. };
  69.  
  70. # see RFC 4880 section 9.1 (ignoring deprecated algorithms for now)
  71. my $asym_algos = { rsa => 1,
  72.            elgamal => 16,
  73.            dsa => 17,
  74.            };
  75.  
  76. # see RFC 4880 section 9.2
  77. my $ciphers = { plaintext => 0,
  78.         idea => 1,
  79.         tripledes => 2,
  80.         cast5 => 3,
  81.         blowfish => 4,
  82.         aes128 => 7,
  83.         aes192 => 8,
  84.         aes256 => 9,
  85.         twofish => 10,
  86.           };
  87.  
  88. # see RFC 4880 section 9.3
  89. my $zips = { uncompressed => 0,
  90.          zip => 1,
  91.          zlib => 2,
  92.          bzip2 => 3,
  93.        };
  94.  
  95. # see RFC 4880 section 9.4
  96. my $digests = { md5 => 1,
  97.         sha1 => 2,
  98.         ripemd160 => 3,
  99.         sha256 => 8,
  100.         sha384 => 9,
  101.         sha512 => 10,
  102.         sha224 => 11,
  103.           };
  104.  
  105. # see RFC 4880 section 5.2.3.21
  106. my $usage_flags = { certify => 0x01,
  107.             sign => 0x02,
  108.             encrypt_comms => 0x04,
  109.             encrypt_storage => 0x08,
  110.             encrypt => 0x0c, ## both comms and storage
  111.             split => 0x10, # the private key is split via secret sharing
  112.             authenticate => 0x20,
  113.             shared => 0x80, # more than one person holds the entire private key
  114.           };
  115.  
  116. # see RFC 4880 section 4.3
  117. my $packet_types = { pubkey_enc_session => 1,
  118.              sig => 2,
  119.              symkey_enc_session => 3,
  120.              onepass_sig => 4,
  121.              seckey => 5,
  122.              pubkey => 6,
  123.              sec_subkey => 7,
  124.              compressed_data => 8,
  125.              symenc_data => 9,
  126.              marker => 10,
  127.              literal => 11,
  128.              trust => 12,
  129.              uid => 13,
  130.              pub_subkey => 14,
  131.              uat => 17,
  132.              symenc_w_integrity => 18,
  133.              mdc => 19,
  134.            };
  135.  
  136. # see RFC 4880 section 5.2.1
  137. my $sig_types = { binary_doc => 0x00,
  138.           text_doc => 0x01,
  139.           standalone => 0x02,
  140.           generic_certification => 0x10,
  141.           persona_certification => 0x11,
  142.           casual_certification => 0x12,
  143.           positive_certification => 0x13,
  144.           subkey_binding => 0x18,
  145.           primary_key_binding => 0x19,
  146.           key_signature => 0x1f,
  147.           key_revocation => 0x20,
  148.           subkey_revocation => 0x28,
  149.           certification_revocation => 0x30,
  150.           timestamp => 0x40,
  151.           thirdparty => 0x50,
  152.         };
  153.  
  154.  
  155. # see RFC 4880 section 5.2.3.23
  156. my $revocation_reasons = { no_reason_specified => 0,
  157.                key_superseded => 1,
  158.                key_compromised => 2,
  159.                key_retired => 3,
  160.                user_id_no_longer_valid => 32,
  161.              };
  162.  
  163. # see RFC 4880 section 5.2.3.1
  164. my $subpacket_types = { sig_creation_time => 2,
  165.             sig_expiration_time => 3,
  166.             exportable => 4,
  167.             trust_sig => 5,
  168.             regex => 6,
  169.             revocable => 7,
  170.             key_expiration_time => 9,
  171.             preferred_cipher => 11,
  172.             revocation_key => 12,
  173.             issuer => 16,
  174.             notation => 20,
  175.             preferred_digest => 21,
  176.             preferred_compression => 22,
  177.             keyserver_prefs => 23,
  178.             preferred_keyserver => 24,
  179.             primary_uid => 25,
  180.             policy_uri => 26,
  181.             usage_flags => 27,
  182.             signers_uid => 28,
  183.             revocation_reason => 29,
  184.             features => 30,
  185.             signature_target => 31,
  186.             embedded_signature => 32,
  187.                };
  188.  
  189. # bitstring (see RFC 4880 section 5.2.3.24)
  190. my $features = { mdc => 0x01
  191.            };
  192.  
  193. # bitstring (see RFC 4880 5.2.3.17)
  194. my $keyserver_prefs = { nomodify => 0x80
  195.               };
  196.  
  197. ###### end lookup tables ######
  198.  
  199. # FIXME: if we want to be able to interpret openpgp data as well as
  200. # produce it, we need to produce key/value-swapped lookup tables as well.
  201.  
  202.  
  203. ########### Math/Utility Functions ##############
  204.  
  205.  
  206. # see the bottom of page 44 of RFC 4880 (http://tools.ietf.org/html/rfc4880#page-44)
  207. sub simple_checksum {
  208.   my $bytes = shift;
  209.  
  210.   return unpack("%16C*",$bytes);
  211. }
  212.  
  213.  
  214. # calculate/print the fingerprint of an openssh-style keyblob:
  215.  
  216. sub sshfpr {
  217.   my $keyblob = shift;
  218.   use Digest::MD5;
  219.   return join(':', map({unpack("H*", $_)} split(//, Digest::MD5::md5($keyblob))));
  220. }
  221.  
  222. # calculate the multiplicative inverse of a mod b this is euclid's
  223. # extended algorithm.  For more information see:
  224. # http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm the
  225. # arguments here should be Crypt::OpenSSL::Bignum objects.  $a should
  226. # be the larger of the two values, and the two values should be
  227. # coprime.
  228.  
  229. sub modular_multi_inverse {
  230.   my $a = shift;
  231.   my $b = shift;
  232.  
  233.  
  234.   my $origdivisor = $b->copy();
  235.  
  236.   my $ctx = Crypt::OpenSSL::Bignum::CTX->new();
  237.   my $x = Crypt::OpenSSL::Bignum->zero();
  238.   my $y = Crypt::OpenSSL::Bignum->one();
  239.   my $lastx = Crypt::OpenSSL::Bignum->one();
  240.   my $lasty = Crypt::OpenSSL::Bignum->zero();
  241.  
  242.   my $finalquotient;
  243.   my $finalremainder;
  244.  
  245.   while (! $b->is_zero()) {
  246.     my ($quotient, $remainder) = $a->div($b, $ctx);
  247.  
  248.     $a = $b;
  249.     $b = $remainder;
  250.  
  251.     my $temp = $x;
  252.     $x = $lastx->sub($quotient->mul($x, $ctx));
  253.     $lastx = $temp;
  254.  
  255.     $temp = $y;
  256.     $y = $lasty->sub($quotient->mul($y, $ctx));
  257.     $lasty = $temp;
  258.   }
  259.  
  260.   if (!$a->is_one()) {
  261.     die "did this math wrong.\n";
  262.   }
  263.  
  264.   # let's make sure that we return a positive value because RFC 4880,
  265.   # section 3.2 only allows unsigned values:
  266.  
  267.   ($finalquotient, $finalremainder) = $lastx->add($origdivisor)->div($origdivisor, $ctx);
  268.  
  269.   return $finalremainder;
  270. }
  271.  
  272.  
  273. ############ OpenPGP formatting functions ############
  274.  
  275. # make an old-style packet out of the given packet type and body.
  276. # old-style  (see RFC 4880 section 4.2)
  277. sub make_packet {
  278.   my $type = shift;
  279.   my $body = shift;
  280.   my $options = shift;
  281.  
  282.   my $len = length($body);
  283.   my $pseudolen = $len;
  284.  
  285.   # if the caller wants to use at least N octets of packet length,
  286.   # pretend that we're using that many.
  287.   if (defined $options && defined $options->{'packet_length'}) {
  288.       $pseudolen = 2**($options->{'packet_length'} * 8) - 1;
  289.   }
  290.   if ($pseudolen < $len) {
  291.       $pseudolen = $len;
  292.   }
  293.  
  294.   my $lenbytes;
  295.   my $lencode;
  296.  
  297.   if ($pseudolen < 2**8) {
  298.     $lenbytes = $old_format_packet_lengths->{one};
  299.     $lencode = 'C';
  300.   } elsif ($pseudolen < 2**16) {
  301.     $lenbytes = $old_format_packet_lengths->{two};
  302.     $lencode = 'n';
  303.   } elsif ($pseudolen < 2**31) {
  304.     ## not testing against full 32 bits because i don't want to deal
  305.     ## with potential overflow.
  306.     $lenbytes = $old_format_packet_lengths->{four};
  307.     $lencode = 'N';
  308.   } else {
  309.     ## what the hell do we do here?
  310.     $lenbytes = $old_format_packet_lengths->{indeterminate};
  311.     $lencode = '';
  312.   }
  313.  
  314.   return pack('C'.$lencode, 0x80 + ($type * 4) + $lenbytes, $len).
  315.     $body;
  316. }
  317.  
  318.  
  319. # takes a Crypt::OpenSSL::Bignum, returns it formatted as OpenPGP MPI
  320. # (RFC 4880 section 3.2)
  321. sub mpi_pack {
  322.   my $num = shift;
  323.  
  324.   my $val = $num->to_bin();
  325.   my $mpilen = length($val)*8;
  326.  
  327. # this is a kludgy way to get the number of significant bits in the
  328. # first byte:
  329.   my $bitsinfirstbyte = length(sprintf("%b", ord($val)));
  330.  
  331.   $mpilen -= (8 - $bitsinfirstbyte);
  332.  
  333.   return pack('n', $mpilen).$val;
  334. }
  335.  
  336. # takes a Crypt::OpenSSL::Bignum, returns an MPI packed in preparation
  337. # for an OpenSSH-style public key format.  see:
  338. # http://marc.info/?l=openssh-unix-dev&m=121866301718839&w=2
  339. sub openssh_mpi_pack {
  340.   my $num = shift;
  341.  
  342.   my $val = $num->to_bin();
  343.   my $mpilen = length($val);
  344.  
  345.   my $ret = pack('N', $mpilen);
  346.  
  347.   # if the first bit of the leading byte is high, we should include a
  348.   # 0 byte:
  349.   if (ord($val) & 0x80) {
  350.     $ret = pack('NC', $mpilen+1, 0);
  351.   }
  352.  
  353.   return $ret.$val;
  354. }
  355.  
  356. sub openssh_pubkey_pack {
  357.   my $key = shift;
  358.  
  359.   my ($modulus, $exponent) = $key->get_key_parameters();
  360.  
  361.   return openssh_mpi_pack(Crypt::OpenSSL::Bignum->new_from_bin("ssh-rsa")).
  362.       openssh_mpi_pack($exponent).
  363.     openssh_mpi_pack($modulus);
  364. }
  365.  
  366. # pull an OpenPGP-specified MPI off of a given stream, returning it as
  367. # a Crypt::OpenSSL::Bignum.
  368. sub read_mpi {
  369.   my $instr = shift;
  370.   my $readtally = shift;
  371.  
  372.   my $bitlen;
  373.   read($instr, $bitlen, 2) or die "could not read MPI length.\n";
  374.   $bitlen = unpack('n', $bitlen);
  375.   $$readtally += 2;
  376.  
  377.   my $bytestoread = POSIX::floor(($bitlen + 7)/8);
  378.   my $ret;
  379.   read($instr, $ret, $bytestoread) or die "could not read MPI body.\n";
  380.   $$readtally += $bytestoread;
  381.   return Crypt::OpenSSL::Bignum->new_from_bin($ret);
  382. }
  383.  
  384.  
  385. # FIXME: genericize these to accept either RSA or DSA keys:
  386. sub make_rsa_pub_key_body {
  387.   my $key = shift;
  388.   my $key_timestamp = shift;
  389.  
  390.   my ($n, $e) = $key->get_key_parameters();
  391.  
  392.   return
  393.     pack('CN', 4, $key_timestamp).
  394.       pack('C', $asym_algos->{rsa}).
  395.     mpi_pack($n).
  396.       mpi_pack($e);
  397. }
  398.  
  399. sub make_rsa_sec_key_body {
  400.   my $key = shift;
  401.   my $key_timestamp = shift;
  402.  
  403.   # we're not using $a and $b, but we need them to get to $c.
  404.   my ($n, $e, $d, $p, $q) = $key->get_key_parameters();
  405.  
  406.   my $c3 = modular_multi_inverse($p, $q);
  407.  
  408.   my $secret_material = mpi_pack($d).
  409.     mpi_pack($p).
  410.       mpi_pack($q).
  411.     mpi_pack($c3);
  412.  
  413.   # according to Crypt::OpenSSL::RSA, the closest value we can get out
  414.   # of get_key_parameters is 1/q mod p; but according to sec 5.5.3 of
  415.   # RFC 4880, we're actually looking for u, the multiplicative inverse
  416.   # of p, mod q.  This is why we're calculating the value directly
  417.   # with modular_multi_inverse.
  418.  
  419.   return
  420.     pack('CN', 4, $key_timestamp).
  421.       pack('C', $asym_algos->{rsa}).
  422.     mpi_pack($n).
  423.       mpi_pack($e).
  424.         pack('C', 0). # seckey material is not encrypted -- see RFC 4880 sec 5.5.3
  425.           $secret_material.
  426.         pack('n', simple_checksum($secret_material));
  427. }
  428.  
  429. # expects an RSA key (public or private) and a timestamp
  430. sub fingerprint {
  431.   my $key = shift;
  432.   my $key_timestamp = shift;
  433.  
  434.   my $rsabody = make_rsa_pub_key_body($key, $key_timestamp);
  435.  
  436.   return Digest::SHA::sha1(pack('Cn', 0x99, length($rsabody)).$rsabody);
  437. }
  438.  
  439.  
  440. # FIXME: handle DSA keys as well!
  441. sub makeselfsig {
  442.   my $rsa = shift;
  443.   my $uid = shift;
  444.   my $args = shift;
  445.  
  446.   # strong assertion of identity is the default (for a self-sig):
  447.   if (! defined $args->{certification_type}) {
  448.     $args->{certification_type} = $sig_types->{positive_certification};
  449.   }
  450.  
  451.   if (! defined $args->{sig_timestamp}) {
  452.     $args->{sig_timestamp} = time();
  453.   }
  454.   my $key_timestamp = $args->{key_timestamp} + 0;
  455.  
  456.   # generate and aggregate subpackets:
  457.  
  458.   # key usage flags:
  459.   my $flags = 0;
  460.   if (! defined $args->{usage_flags}) {
  461.     $flags = $usage_flags->{certify};
  462.   } else {
  463.     my @ff = split(",", $args->{usage_flags});
  464.     foreach my $f (@ff) {
  465.       if (! defined $usage_flags->{$f}) {
  466.     die "No such flag $f";
  467.       }
  468.       $flags |= $usage_flags->{$f};
  469.     }
  470.   }
  471.   my $usage_subpacket = pack('CCC', 2, $subpacket_types->{usage_flags}, $flags);
  472.  
  473.   # how should we determine how far off to set the expiration date?
  474.   # default is no expiration.  Specify the timestamp in seconds from the
  475.   # key creation.
  476.   my $expiration_subpacket = '';
  477.   if (defined $args->{expiration}) {
  478.     my $expires_in = $args->{expiration} + 0;
  479.     $expiration_subpacket = pack('CCN', 5, $subpacket_types->{key_expiration_time}, $expires_in);
  480.   }
  481.  
  482.  
  483.   # prefer AES-256, AES-192, AES-128, CAST5, 3DES:
  484.   my $pref_sym_algos = pack('CCCCCCC', 6, $subpacket_types->{preferred_cipher},
  485.                 $ciphers->{aes256},
  486.                 $ciphers->{aes192},
  487.                 $ciphers->{aes128},
  488.                 $ciphers->{cast5},
  489.                 $ciphers->{tripledes}
  490.                );
  491.  
  492.   # prefer SHA-512, SHA-384, SHA-256, SHA-224, RIPE-MD/160, SHA-1
  493.   my $pref_hash_algos = pack('CCCCCCCC', 7, $subpacket_types->{preferred_digest},
  494.                  $digests->{sha512},
  495.                  $digests->{sha384},
  496.                  $digests->{sha256},
  497.                  $digests->{sha224},
  498.                  $digests->{ripemd160},
  499.                  $digests->{sha1}
  500.                 );
  501.  
  502.   # prefer ZLIB, BZip2, ZIP
  503.   my $pref_zip_algos = pack('CCCCC', 4, $subpacket_types->{preferred_compression},
  504.                 $zips->{zlib},
  505.                 $zips->{bzip2},
  506.                 $zips->{zip}
  507.                );
  508.  
  509.   # we support the MDC feature:
  510.   my $feature_subpacket = pack('CCC', 2, $subpacket_types->{features},
  511.                    $features->{mdc});
  512.  
  513.   # keyserver preference: only owner modify (???):
  514.   my $keyserver_pref = pack('CCC', 2, $subpacket_types->{keyserver_prefs},
  515.                 $keyserver_prefs->{nomodify});
  516.  
  517.  
  518.   $args->{hashed_subpackets} =
  519.       $usage_subpacket.
  520.     $expiration_subpacket.
  521.       $pref_sym_algos.
  522.         $pref_hash_algos.
  523.           $pref_zip_algos.
  524.         $feature_subpacket.
  525.           $keyserver_pref;
  526.  
  527.   return gensig($rsa, $uid, $args);
  528. }
  529.  
  530. # FIXME: handle non-RSA keys
  531.  
  532. # FIXME: this currently only makes self-sigs -- we should parameterize
  533. # it to make certifications over keys other than the issuer.
  534. sub gensig {
  535.   my $rsa = shift;
  536.   my $uid = shift;
  537.   my $args = shift;
  538.  
  539.   # FIXME: allow signature creation using digests other than SHA256
  540.   $rsa->use_sha256_hash();
  541.  
  542.   # see page 22 of RFC 4880 for why i think this is the right padding
  543.   # choice to use:
  544.   $rsa->use_pkcs1_padding();
  545.  
  546.   if (! $rsa->check_key()) {
  547.     die "key does not check\n";
  548.   }
  549.  
  550.   my $certtype = $args->{certification_type} + 0;
  551.  
  552.   my $version = pack('C', 4);
  553.   my $sigtype = pack('C', $certtype);
  554.   # RSA
  555.   my $pubkey_algo = pack('C', $asym_algos->{rsa});
  556.   # SHA256 FIXME: allow signature creation using digests other than SHA256
  557.   my $hash_algo = pack('C', $digests->{sha256});
  558.  
  559.   # FIXME: i'm worried about generating a bazillion new OpenPGP
  560.   # certificates from the same key, which could easily happen if you run
  561.   # this script more than once against the same key (because the
  562.   # timestamps will differ).  How can we prevent this?
  563.  
  564.   # this argument (if set) overrides the current time, to
  565.   # be able to create a standard key.  If we read the key from a file
  566.   # instead of stdin, should we use the creation time on the file?
  567.   my $sig_timestamp = ($args->{sig_timestamp} + 0);
  568.   my $key_timestamp = ($args->{key_timestamp} + 0);
  569.  
  570.   if ($key_timestamp > $sig_timestamp) {
  571.     die "key timestamp must not be later than signature timestamp\n";
  572.   }
  573.  
  574.   my $creation_time_packet = pack('CCN', 5, $subpacket_types->{sig_creation_time}, $sig_timestamp);
  575.  
  576.   my $hashed_subs = $creation_time_packet.$args->{hashed_subpackets};
  577.  
  578.   my $subpacket_octets = pack('n', length($hashed_subs));
  579.  
  580.   my $sig_data_to_be_hashed =
  581.     $version.
  582.       $sigtype.
  583.     $pubkey_algo.
  584.       $hash_algo.
  585.         $subpacket_octets.
  586.           $hashed_subs;
  587.  
  588.   my $pubkey = make_rsa_pub_key_body($rsa, $key_timestamp);
  589.  
  590.   # this is for signing.  it needs to be an old-style header with a
  591.   # 2-packet octet count.
  592.  
  593.   my $key_data = make_packet($packet_types->{pubkey}, $pubkey, {'packet_length'=>2});
  594.  
  595.   # take the last 8 bytes of the fingerprint as the keyid:
  596.   my $keyid = substr(fingerprint($rsa, $key_timestamp), 20 - 8, 8);
  597.  
  598.   # the v4 signature trailer is:
  599.  
  600.   # version number, literal 0xff, and then a 4-byte count of the
  601.   # signature data itself.
  602.   my $trailer = pack('CCN', 4, 0xff, length($sig_data_to_be_hashed));
  603.  
  604.   my $uid_data =
  605.     pack('CN', 0xb4, length($uid)).
  606.       $uid;
  607.  
  608.   my $datatosign =
  609.     $key_data.
  610.       $uid_data.
  611.     $sig_data_to_be_hashed.
  612.       $trailer;
  613.  
  614.   # FIXME: handle signatures over digests other than SHA256:
  615.   my $data_hash = Digest::SHA::sha256_hex($datatosign);
  616.  
  617.   my $issuer_packet = pack('CCa8', 9, $subpacket_types->{issuer}, $keyid);
  618.  
  619.   my $sig = Crypt::OpenSSL::Bignum->new_from_bin($rsa->sign($datatosign));
  620.  
  621.   my $sig_body =
  622.     $sig_data_to_be_hashed.
  623.       pack('n', length($issuer_packet)).
  624.     $issuer_packet.
  625.       pack('n', hex(substr($data_hash, 0, 4))).
  626.         mpi_pack($sig);
  627.  
  628.   return make_packet($packet_types->{sig}, $sig_body);
  629. }
  630.  
  631. # FIXME: switch to passing the whole packet as the arg, instead of the
  632. # input stream.
  633.  
  634. # FIXME: think about native perl representation of the packets instead.
  635.  
  636. # Put a user ID into the $data
  637. sub finduid {
  638.   my $data = shift;
  639.   my $instr = shift;
  640.   my $tag = shift;
  641.   my $packetlen = shift;
  642.  
  643.   my $dummy;
  644.   ($tag == $packet_types->{uid}) or die "This should not be called on anything but a User ID packet\n";
  645.  
  646.   read($instr, $dummy, $packetlen);
  647.   $data->{uid}->{$dummy} = {};
  648.   $data->{current}->{uid} = $dummy;
  649. }
  650.  
  651.  
  652. # find signatures associated with the given fingerprint and user ID.
  653. sub findsig {
  654.   my $data = shift;
  655.   my $instr = shift;
  656.   my $tag = shift;
  657.   my $packetlen = shift;
  658.  
  659.   ($tag == $packet_types->{sig}) or die "No calling findsig on anything other than a signature packet.\n";
  660.  
  661.   my $dummy;
  662.   my $readbytes = 0;
  663.  
  664.   read($instr, $dummy, $packetlen - $readbytes) or die "Could not read in this packet.\n";
  665.  
  666.   if ((! defined $data->{key}) ||
  667.       (! defined $data->{uid}) ||
  668.       (! defined $data->{uid}->{$data->{target}->{uid}})) {
  669.     # the user ID we are looking for has not been found yet.
  670.     return;
  671.   }
  672.  
  673.   # FIXME: if we get two primary keys on stdin, both with the same
  674.   # targetd user ID, we'll store signatures from both keys, which is
  675.   # probably wrong.
  676.  
  677.   # the current ID is not what we're looking for:
  678.   return if ($data->{current}->{uid} ne $data->{target}->{uid});
  679.  
  680.   # just storing the raw signatures for the moment:
  681.   push @{$data->{sigs}}, make_packet($packet_types->{sig}, $dummy);
  682.   return;
  683.  
  684. }
  685.  
  686. # given an input stream and data, store the found key in data and
  687. # consume the rest of the stream corresponding to the packet.
  688. # data contains: (fpr: fingerprint to find, key: current best guess at key)
  689. sub findkey {
  690.   my $data = shift;
  691.   my $instr = shift;
  692.   my $tag = shift;
  693.   my $packetlen = shift;
  694.  
  695.   my $dummy;
  696.   my $ver;
  697.   my $readbytes = 0;
  698.  
  699.   read($instr, $ver, 1) or die "could not read key version\n";
  700.   $readbytes += 1;
  701.   $ver = ord($ver);
  702.  
  703.   if ($ver != 4) {
  704.     printf(STDERR "We only work with version 4 keys.  This key appears to be version %s.\n", $ver);
  705.     read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n";
  706.     return;
  707.   }
  708.  
  709.   my $key_timestamp;
  710.   read($instr, $key_timestamp, 4) or die "could not read key timestamp.\n";
  711.   $readbytes += 4;
  712.   $key_timestamp = unpack('N', $key_timestamp);
  713.  
  714.   my $algo;
  715.   read($instr, $algo, 1) or die "could not read key algorithm.\n";
  716.   $readbytes += 1;
  717.   $algo = ord($algo);
  718.   if ($algo != $asym_algos->{rsa}) {
  719.     printf(STDERR "We only support RSA keys (this key used algorithm %d).\n", $algo);
  720.     read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n";
  721.     return;
  722.   }
  723.  
  724.   ## we have an RSA key.
  725.   my $modulus = read_mpi($instr, \$readbytes);
  726.   my $exponent = read_mpi($instr, \$readbytes);
  727.  
  728.   my $pubkey = Crypt::OpenSSL::RSA->new_key_from_parameters($modulus, $exponent);
  729.   my $foundfpr = fingerprint($pubkey, $key_timestamp);
  730.  
  731.   my $foundfprstr = Crypt::OpenSSL::Bignum->new_from_bin($foundfpr)->to_hex();
  732.   # left-pad with 0's to bring up to full 40-char (160-bit) fingerprint:
  733.   $foundfprstr = sprintf("%040s", $foundfprstr);
  734.   my $matched = 0;
  735.  
  736.   # is this a match?
  737.   if ((!defined($data->{target}->{fpr})) ||
  738.       (substr($foundfprstr, -1 * length($data->{target}->{fpr})) eq $data->{target}->{fpr})) {
  739.     if (defined($data->{key})) {
  740.       die "Found two matching keys.\n";
  741.     }
  742.     $data->{key} = { 'rsa' => $pubkey,
  743.              'timestamp' => $key_timestamp };
  744.     $matched = 1;
  745.   }
  746.  
  747.   if ($tag != $packet_types->{seckey} &&
  748.       $tag != $packet_types->{sec_subkey}) {
  749.     if ($readbytes < $packetlen) {
  750.       read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n";
  751.     }
  752.     return;
  753.   }
  754.   if (!$matched) {
  755.     # we don't think the public part of this key matches
  756.     if ($readbytes < $packetlen) {
  757.       read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n";
  758.     }
  759.     return;
  760.   }
  761.  
  762.   my $s2k;
  763.   read($instr, $s2k, 1) or die "Could not read S2K octet.\n";
  764.   $readbytes += 1;
  765.   $s2k = ord($s2k);
  766.   if ($s2k != 0) {
  767.     printf(STDERR "We cannot handle encrypted secret keys.  Skipping!\n") ;
  768.     read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n";
  769.     return;
  770.   }
  771.  
  772.   # secret material is unencrypted
  773.   # see http://tools.ietf.org/html/rfc4880#section-5.5.3
  774.   my $d = read_mpi($instr, \$readbytes);
  775.   my $p = read_mpi($instr, \$readbytes);
  776.   my $q = read_mpi($instr, \$readbytes);
  777.   my $u = read_mpi($instr, \$readbytes);
  778.  
  779.   my $checksum;
  780.   read($instr, $checksum, 2) or die "Could not read checksum of secret key material.\n";
  781.   $readbytes += 2;
  782.   $checksum = unpack('n', $checksum);
  783.  
  784.   # FIXME: compare with the checksum!  how?  the data is
  785.   # gone into the Crypt::OpenSSL::Bignum
  786.  
  787.   $data->{key}->{rsa} = Crypt::OpenSSL::RSA->new_key_from_parameters($modulus,
  788.                                      $exponent,
  789.                                      $d,
  790.                                      $p,
  791.                                      $q);
  792.  
  793.   $data->{key}->{rsa}->check_key() or die "Secret key is not a valid RSA key.\n";
  794.  
  795.   if ($readbytes < $packetlen) {
  796.     read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n";
  797.   }
  798. }
  799.  
  800. sub openpgp2rsa {
  801.   my $instr = shift;
  802.   my $fpr = shift;
  803.  
  804.   if (defined $fpr) {
  805.     if (length($fpr) < 8) {
  806.       die "We need at least 8 hex digits of fingerprint.\n";
  807.     }
  808.     $fpr = uc($fpr);
  809.   }
  810.  
  811.   my $data = { target => { fpr => $fpr,
  812.              },
  813.            };
  814.   my $subs = { $packet_types->{pubkey} => \&findkey,
  815.            $packet_types->{pub_subkey} => \&findkey,
  816.            $packet_types->{seckey} => \&findkey,
  817.            $packet_types->{sec_subkey} => \&findkey };
  818.  
  819.   packetwalk($instr, $subs, $data);
  820.  
  821.   return $data->{key}->{rsa};
  822. }
  823.  
  824. sub findkeyfprs {
  825.   my $data = shift;
  826.   my $instr = shift;
  827.   my $tag = shift;
  828.   my $packetlen = shift;
  829.  
  830.   findkey($data, $instr, $tag, $packetlen);
  831.   if (defined($data->{key})) {
  832.     if (defined($data->{key}->{rsa}) && defined($data->{key}->{timestamp})) {
  833.       $data->{keys}->{fingerprint($data->{key}->{rsa}, $data->{key}->{timestamp})} = $data->{key};
  834.     } else {
  835.       die "should have found some key here";
  836.     }
  837.     undef($data->{key});
  838.   }
  839. };
  840.  
  841. sub getallprimarykeys {
  842.   my $instr = shift;
  843.  
  844.   my $subs = { $packet_types->{pubkey} => \&findkeyfprs,
  845.                $packet_types->{seckey} => \&findkeyfprs,
  846.          };
  847.   my $data = {target => { } };
  848.  
  849.   packetwalk($instr, $subs, $data);
  850.  
  851.   if (defined $data->{keys}) {
  852.     return $data->{keys};
  853.   } else {
  854.     return {};
  855.   }
  856. }
  857.  
  858. sub adduserid {
  859.   my $instr = shift;
  860.   my $fpr = shift;
  861.   my $uid = shift;
  862.   my $args = shift;
  863.  
  864.   if ((! defined $fpr) ||
  865.       (length($fpr) < 8)) {
  866.     die "We need at least 8 hex digits of fingerprint.\n";
  867.   }
  868.  
  869.   $fpr = uc($fpr);
  870.  
  871.   if (! defined $uid) {
  872.     die "No User ID defined.\n";
  873.   }
  874.  
  875.   my $data = { target => { fpr => $fpr,
  876.                uid => $uid,
  877.              },
  878.          };
  879.   my $subs = { $packet_types->{seckey} => \&findkey,
  880.            $packet_types->{uid} => \&finduid,
  881.            $packet_types->{sig} => \&findsig,
  882.          };
  883.  
  884.   packetwalk($instr, $subs, $data);
  885.  
  886.   if ((! defined $data->{key}) ||
  887.       (! defined $data->{key}->{rsa}) ||
  888.       (! defined $data->{key}->{timestamp})) {
  889.     die "The key requested was not found.\n"
  890.   }
  891.  
  892.   if (defined $data->{uid}->{$uid}) {
  893.     die "The requested User ID '$uid' is already associated with this key.\n";
  894.   }
  895.   $args->{key_timestamp} = $data->{key}->{timestamp};
  896.  
  897.   return
  898.     make_packet($packet_types->{pubkey}, make_rsa_pub_key_body($data->{key}->{rsa}, $data->{key}->{timestamp})).
  899.       make_packet($packet_types->{uid}, $uid).
  900.     makeselfsig($data->{key}->{rsa},
  901.             $uid,
  902.             $args);
  903.  
  904. }
  905.  
  906.  
  907. sub revokeuserid {
  908.   my $instr = shift;
  909.   my $fpr = shift;
  910.   my $uid = shift;
  911.   my $sigtime = shift;
  912.  
  913.   if ((! defined $fpr) ||
  914.       (length($fpr) < 8)) {
  915.     die "We need at least 8 hex digits of fingerprint.\n";
  916.   }
  917.  
  918.   $fpr = uc($fpr);
  919.  
  920.   if (! defined $uid) {
  921.     die "No User ID defined.\n";
  922.   }
  923.  
  924.   my $data = { target => { fpr => $fpr,
  925.                uid => $uid,
  926.              },
  927.          };
  928.   my $subs = { $packet_types->{seckey} => \&findkey,
  929.            $packet_types->{uid} => \&finduid,
  930.            $packet_types->{sig} => \&findsig,
  931.          };
  932.  
  933.   packetwalk($instr, $subs, $data);
  934.  
  935.   if ((! defined $data->{uid}) ||
  936.       (! defined $data->{uid}->{$uid})) {
  937.     die "The User ID \"$uid\" is not associated with this key";
  938.   }
  939.  
  940.   if ((! defined $data->{key}) ||
  941.       (! defined $data->{key}->{rsa}) ||
  942.       (! defined $data->{key}->{timestamp})) {
  943.     die "The key requested was not found."
  944.   }
  945.  
  946.   my $revocation_reason = 'No longer using this hostname';
  947.   if (defined $data->{revocation_reason}) {
  948.     $revocation_reason = $data->{revocation_reason};
  949.   }
  950.  
  951.   my $rev_reason_subpkt = prefixsubpacket(pack('CC',
  952.                            $subpacket_types->{revocation_reason},
  953.                            $revocation_reasons->{user_id_no_longer_valid}).
  954.                       $revocation_reason);
  955.  
  956.   if (! defined $sigtime) {
  957.     $sigtime = time();
  958.   }
  959.   # what does a signature like this look like?
  960.   my $args = { key_timestamp => $data->{key}->{timestamp},
  961.            sig_timestamp => $sigtime,
  962.            certification_type => $sig_types->{certification_revocation},
  963.            hashed_subpackets => $rev_reason_subpkt,
  964.          };
  965.  
  966.   return
  967.     make_packet($packet_types->{pubkey}, make_rsa_pub_key_body($data->{key}->{rsa}, $data->{key}->{timestamp})).
  968.       make_packet($packet_types->{uid}, $uid).
  969.     join('', @{$data->{sigs}}).
  970.       gensig($data->{key}->{rsa}, $uid, $args);
  971. }
  972.  
  973.  
  974. # see 5.2.3.1 for tips on how to calculate the length of a subpacket:
  975. sub prefixsubpacket {
  976.   my $subpacket = shift;
  977.  
  978.   my $len = length($subpacket);
  979.   my $prefix;
  980.   use bytes;
  981.   if ($len < 192) {
  982.     # one byte:
  983.     $prefix = pack('C', $len);
  984.   } elsif ($len < 16576) {
  985.     my $in = $len - 192;
  986.     my $second = $in%256;
  987.     my $first = ($in - $second)>>8;
  988.     $prefix = pack('CC', $first + 192, $second)
  989.   } else {
  990.     $prefix = pack('CN', 255, $len);
  991.   }
  992.   return $prefix.$subpacket;
  993. }
  994.  
  995.  
  996.  
  997. sub packetwalk {
  998.   my $instr = shift;
  999.   my $subs = shift;
  1000.   my $data = shift;
  1001.  
  1002.   my $packettag;
  1003.   my $dummy;
  1004.   my $tag;
  1005.  
  1006.   while (! eof($instr)) {
  1007.     read($instr, $packettag, 1);
  1008.     $packettag = ord($packettag);
  1009.  
  1010.     my $packetlen;
  1011.     if ( ! (0x80 & $packettag)) {
  1012.       die "This is not an OpenPGP packet\n";
  1013.     }
  1014.     if (0x40 & $packettag) {
  1015.       # this is a new-format packet.
  1016.       $tag = (0x3f & $packettag);
  1017.       my $nextlen = 0;
  1018.       read($instr, $nextlen, 1);
  1019.       $nextlen = ord($nextlen);
  1020.       if ($nextlen < 192) {
  1021.     $packetlen = $nextlen;
  1022.       } elsif ($nextlen < 224) {
  1023.     my $newoct;
  1024.     read($instr, $newoct, 1);
  1025.     $newoct = ord($newoct);
  1026.     $packetlen = (($nextlen - 192) << 8) + ($newoct) + 192;
  1027.       } elsif ($nextlen == 255) {
  1028.     read($instr, $nextlen, 4);
  1029.     $packetlen = unpack('N', $nextlen);
  1030.       } else {
  1031.     # packet length is undefined.
  1032.       }
  1033.     } else {
  1034.       # this is an old-format packet.
  1035.       my $lentype;
  1036.       $lentype = 0x03 & $packettag;
  1037.       $tag = ( 0x3c & $packettag ) >> 2;
  1038.       if ($lentype == 0) {
  1039.     read($instr, $packetlen, 1) or die "could not read packet length\n";
  1040.     $packetlen = unpack('C', $packetlen);
  1041.       } elsif ($lentype == 1) {
  1042.     read($instr, $packetlen, 2) or die "could not read packet length\n";
  1043.     $packetlen = unpack('n', $packetlen);
  1044.       } elsif ($lentype == 2) {
  1045.     read($instr, $packetlen, 4) or die "could not read packet length\n";
  1046.     $packetlen = unpack('N', $packetlen);
  1047.       } else {
  1048.     # packet length is undefined.
  1049.       }
  1050.     }
  1051.  
  1052.     if (! defined($packetlen)) {
  1053.       die "Undefined packet lengths are not supported.\n";
  1054.     }
  1055.  
  1056.     if (defined $subs->{$tag}) {
  1057.       $subs->{$tag}($data, $instr, $tag, $packetlen);
  1058.     } else {
  1059.       read($instr, $dummy, $packetlen) or die "Could not skip past this packet!\n";
  1060.     }
  1061.   }
  1062.  
  1063.   return $data->{key};
  1064. }
  1065.  
  1066.  
  1067. for (basename($0)) {
  1068.   if (/^pem2openpgp$/) {
  1069.     my $rsa;
  1070.     my $stdin;
  1071.  
  1072.     my $uid = shift;
  1073.     defined($uid) or die "You must specify a user ID string.\n";
  1074.  
  1075.     # FIXME: fail if there is no given user ID; or should we default to
  1076.     # hostname_long() from Sys::Hostname::Long ?
  1077.  
  1078.     if (defined $ENV{PEM2OPENPGP_NEWKEY}) {
  1079.       $rsa = Crypt::OpenSSL::RSA->generate_key($ENV{PEM2OPENPGP_NEWKEY});
  1080.     } else {
  1081.       $stdin = do {
  1082.     local $/; # slurp!
  1083.     <STDIN>;
  1084.       };
  1085.  
  1086.       $rsa = Crypt::OpenSSL::RSA->new_private_key($stdin);
  1087.     }
  1088.  
  1089.     my $key_timestamp = $ENV{PEM2OPENPGP_KEY_TIMESTAMP};
  1090.     my $sig_timestamp = $ENV{PEM2OPENPGP_TIMESTAMP};
  1091.     $sig_timestamp = time() if (!defined $sig_timestamp);
  1092.     $key_timestamp = $sig_timestamp if (!defined $key_timestamp);
  1093.  
  1094.     print
  1095.       make_packet($packet_types->{seckey}, make_rsa_sec_key_body($rsa, $key_timestamp)).
  1096.     make_packet($packet_types->{uid}, $uid).
  1097.       makeselfsig($rsa,
  1098.               $uid,
  1099.               { sig_timestamp => $sig_timestamp,
  1100.             key_timestamp => $key_timestamp,
  1101.             expiration => $ENV{PEM2OPENPGP_EXPIRATION},
  1102.             usage_flags => $ENV{PEM2OPENPGP_USAGE_FLAGS},
  1103.               }
  1104.              );
  1105.   }
  1106.   elsif (/^openpgp2ssh$/) {
  1107.       my $fpr = shift;
  1108.       my $instream;
  1109.       open($instream,'-');
  1110.       binmode($instream, ":bytes");
  1111.       my $key = openpgp2rsa($instream, $fpr);
  1112.       if (defined($key)) {
  1113.     if ($key->is_private()) {
  1114.       print $key->get_private_key_string();
  1115.     } else {
  1116.       print "ssh-rsa ".encode_base64(openssh_pubkey_pack($key), '')."\n";
  1117.     }
  1118.       } else {
  1119.     die "No matching key found.\n";
  1120.       }
  1121.   }
  1122.   elsif (/^keytrans$/) {
  1123.     # subcommands when keytrans is invoked directly are UNSUPPORTED,
  1124.     # UNDOCUMENTED, and WILL NOT BE MAINTAINED.
  1125.     my $subcommand = shift;
  1126.     for ($subcommand) {
  1127.       if (/^revokeuserid$/) {
  1128.     my $fpr = shift;
  1129.     my $uid = shift;
  1130.     my $instream;
  1131.     open($instream,'-');
  1132.     binmode($instream, ":bytes");
  1133.  
  1134.     my $revcert = revokeuserid($instream, $fpr, $uid, $ENV{PEM2OPENPGP_TIMESTAMP});
  1135.  
  1136.     print $revcert;
  1137.       } elsif (/^adduserid$/) {
  1138.     my $fpr = shift;
  1139.     my $uid = shift;
  1140.     my $instream;
  1141.     open($instream,'-');
  1142.     binmode($instream, ":bytes");
  1143.     my $newuid = adduserid($instream, $fpr, $uid, 
  1144.                    { sig_timestamp => $ENV{PEM2OPENPGP_TIMESTAMP},
  1145.                  expiration => $ENV{PEM2OPENPGP_EXPIRATION},
  1146.                  usage_flags => $ENV{PEM2OPENPGP_USAGE_FLAGS},
  1147.                    });
  1148.  
  1149.     print $newuid;
  1150.       } elsif (/^listfprs$/) {
  1151.         my $instream;
  1152.     open($instream,'-');
  1153.     binmode($instream, ":bytes");
  1154.         my $keys = getallprimarykeys($instream);
  1155.         printf("%s\n", join("\n", map { uc(unpack('H*', $_)) } keys(%{$keys})));
  1156.       } elsif (/^sshfpr$/) {
  1157.         use MIME::Base64;
  1158.         my $b64keyblob;
  1159.         my $dummy;
  1160.         while (($dummy,$b64keyblob) = split(/ /, <STDIN>)) {
  1161.           printf("%s\n", sshfpr(decode_base64($b64keyblob)));
  1162.         }
  1163.       } elsif (/^openpgp2sshfpr$/) {
  1164.         my $fpr = shift;
  1165.         my $instream;
  1166.         open($instream,'-');
  1167.         binmode($instream, ":bytes");
  1168.         my $key = openpgp2rsa($instream, $fpr);
  1169.         if (defined($key)) {
  1170.           # openssh uses MD5 for key fingerprints:
  1171.           printf("%d %s %s\n",
  1172.                  $key->size() * 8, # size() is in bytes -- we want bits
  1173.                  sshfpr(openssh_pubkey_pack($key)),
  1174.                  '(RSA)', # FIXME when we support other than RSA.
  1175.                 );
  1176.         } else {
  1177.           die "No matching key found.\n";
  1178.         }
  1179.       } else {
  1180.     die "Unrecognized subcommand.  keytrans subcommands are not a stable interface!\n";
  1181.       }
  1182.     }
  1183.   }
  1184.   else {
  1185.     die "Unrecognized keytrans call.\n";
  1186.   }
  1187. }
  1188.  
  1189.